package net.cyndeline.scalarlib.rldungeon.grammar.production

import scalax.collection.GraphEdge.UnDiEdge
import scalax.collection.immutable.Graph
import net.cyndeline.scalarlib.rlgraph.subgraph.isomorphism.{IsomorphicMapping, NegativeCondition, ElementEquivalence}
import net.cyndeline.scalarlib.rldungeon.grammar.util.{MorphismFactory, Morphism}
import net.cyndeline.scalarlib.rldungeon.grammar.ComponentProduction
import scala.reflect.runtime.universe._
import scala.reflect.ClassTag
import scala.util.Random
import net.cyndeline.scalarlib.rlgraph.subgraph.isomorphism.jGraphT.SubGraphIsomorphismInspector
import net.cyndeline.scalarlib.rldungeon.dgs.graph.{DGSFunctionality, Room}
import scalax.collection.GraphPredef.OuterEdge

/**
 * Specifies a single left-hand side in a graph grammar production, and a single right-hand side.
 *
 * @param pattern A graph whose vertices and edges matches the sought-after topology to apply the production to. Edges
 *                will only be taken into account in the manner they connect vertices, not by checking edge-specific
 *                data. Must be a non-empty graph.
 * @param matcher Compares vertices in the pattern with vertices in the input graph to see if the graph contains a
 *                sub graph whose topology matches the pattern.
 * @param negativeCondition Specifies properties that the supplied graph is not allowed to have even though its
 *                          topology matched the pattern.
 * @param compProduction Applies a modification to a graph based on a morphism between the patterns vertices
 *                       and the graph.
 * @param random Used to select ComponentProductions randomly.
 * @param isomorphismMapper Examines every supplied graph for sub-structures that matches the pattern, and returns
 *                          one of them.
 * @param morphismFactory Factory responsible for building morphisms based on isomorphic mappings.
 */
final class SingleProduction[VType <: Room[VType]: TypeTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]: ({type l[M[_]] = TypeTag[M[VType]]})#l : ({type l[M[_]] = ClassTag[M[VType]]})#l]
                                                              (pattern: Graph[VType, EType],
                                                               matcher: ElementEquivalence[VType, EType],
                                                               negativeCondition: Option[NegativeCondition[VType, EType]],
                                                               compProduction: ComponentProduction[VType, EType],
                                                               random: Random,
                                                               isomorphismMapper: IsomorphicMapping,
                                                               morphismFactory: MorphismFactory)
                                                               extends Production[VType, EType](pattern, matcher, negativeCondition, random, isomorphismMapper, morphismFactory) {

  /**
   * Constructs a new production using default pattern matching objects.
   *
   * @param pattern A graph whose vertices and edges matches the sought-after topology to apply the production to. Edges
   *                will only be taken into account in the manner they connect vertices, not by checking edge-specific
   *                data. Must be a non-empty graph.
   * @param matcher Compares vertices in the pattern with vertices in the input graph to see if the graph contains a
   *                sub graph whose topology matches the pattern.
   * @param negativeCondition Specifies properties that the supplied graph is not allowed to have even though its
   *                          topology matched the pattern.
   * @param compProduction Applies a modification to a graph based on a morphism between the patterns vertices
   *                       and the graph.
   * @param random Used to select ComponentProductions randomly.
   */
  def this(pattern: Graph[VType, EType],
           matcher: ElementEquivalence[VType, EType],
           negativeCondition: Option[NegativeCondition[VType, EType]],
           compProduction: ComponentProduction[VType, EType],
           random: Random) = this(pattern, matcher, negativeCondition, compProduction, random, new SubGraphIsomorphismInspector(), new MorphismFactory())

  /**
   * Constructs a new production, taking only the user-supplied classes without a negative condition.
   *
   * @param pattern A graph whose vertices and edges matches the sought-after topology to apply the production to. Edges
   *                will only be taken into account in the manner they connect vertices, not by checking edge-specific
   *                data. Must be a non-empty graph.
   * @param matcher Compares vertices in the pattern with vertices in the input graph to see if the graph contains a
   *                sub graph whose topology matches the pattern.
   * @param compProduction Applies a modification to a graph based on a morphism between the patterns vertices
   *                       and the graph.
   * @param random Used to select ComponentProductions randomly.
   */
  def this(pattern: Graph[VType, EType],
           matcher: ElementEquivalence[VType, EType],
           compProduction: ComponentProduction[VType, EType],
           random: Random) = this(pattern, matcher, None, compProduction, random, new SubGraphIsomorphismInspector(), new MorphismFactory())

  /**
   * Modifies a graph.
   *
   * @param graph Graph to modify.
   * @return the graph resulting from modification if one was performed, otherwise None.
   */
  override def apply(graph: Graph[VType, EType]): Option[Graph[VType, EType]] = {
    val matchingVertices: Option[Morphism[VType]] = getMatch(graph)

    if (matchingVertices.isDefined) {
      Some(compProduction.apply(matchingVertices.get, graph))
    } else {
      None
    }
  }
}
